package com.example.exceldemo.utils;

import com.example.exceldemo.exception.ServiceException;
import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.io.ClassPathResource;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.util.Map;


public class PdfUtil {
    private static final Logger log = LoggerFactory.getLogger(PdfUtil.class);
    // 利用模板生成pdf

    /**
     * 根据模板和数据生成PDF文档并保存到本地
     *
     * @param data 包含需要填充到模板中的数据的键值对
     * @param map 包含附加的字节数组，可能用于嵌入到PDF中
     * @param templatePath 模板文件的路径
     * @return 生成的PDF文档的字节数组
     * @throws ServiceException 如果模板文件不存在或读取模板失败，抛出此异常
     */
    public static byte[] generatePdfToLocal(Map<String, Object> data, Map<String, byte[]> map,String templatePath) {
        // 模板路径
        try (InputStream template = new ClassPathResource(templatePath).getInputStream()) {
            if (template == null)
                throw new ServiceException("模板文件不存在");
            return processPdf(template, data, map);
        } catch (Exception e) {
            // 如果读取模板过程中发生错误，记录错误日志并抛出异常
            log.error("读取模板失败", e);
            throw new ServiceException("读取模板失败: " + e.getMessage());
        }
    }

    /**
     * 处理PDF文档的方法
     * 该方法通过输入流读取一个PDF模板，并根据提供的数据和图片字典对模板进行填充和处理，最终返回处理后的PDF字节数组
     *
     * @param templatePath PDF模板的输入流，用于读取PDF模板文件
     * @param data 包含填充数据的Map，用于在PDF中替换占位符
     * @param map 包含图片字节数组的Map，用于在PDF中插入图片
     * @return 处理后的PDF文档的字节数组
     */
    private static byte[] processPdf(InputStream templatePath, Map<String, Object> data, Map<String, byte[]> map) throws IOException, DocumentException {
        // 定义PDF阅读器、字节输出流和PDF印章对象
        PdfReader reader;
        ByteArrayOutputStream bos;
        PdfStamper stamper;
        try {
            // 设置输出字体，指定字体文件路径和编码方式
            BaseFont bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
            // 读取PDF模板文件
            reader = new PdfReader(templatePath);
            log.info("开始读取pdf模板....");
            bos = new ByteArrayOutputStream();
            // 创建PDF印章对象，用于在PDF上进行操作
            stamper = new PdfStamper(reader, bos);
            // 获取PDF表单对象，用于填充表单字段
            AcroFields form = stamper.getAcroFields();
            // 为表单添加替代字体，确保中文显示正常
            form.addSubstitutionFont(bf);
            // 向PDF中添加图片
            addImagesToPdf(form, map,stamper);
            // 向PDF中添加文本和表格类内容
            addTextToPdf(form, data, bf,stamper);
            // 使生成的PDF内容不可编辑
            stamper.setFormFlattening(true);
            stamper.close();
            // 返回处理后的PDF内容的字节数组
            return bos.toByteArray();
        } catch (IOException | DocumentException e) {
            // 如果发生异常，记录错误日志并抛出自定义服务异常
            log.error("生成pdf失败", e);
            throw new ServiceException("生成pdf失败: " + e.getMessage());
        }
    }

    /**
     * 将图像添加到PDF文件中
     *
     * @param form PDF文件中的表单字段
     * @param map 图像资源映射，键为字段标识，值为图像字节数组
     * @param stamper PDF文件的印章对象，用于在现有PDF上添加内容
     * 该方法遍历图像资源映射，将指定字段标识的图像添加到PDF的指定位置
     */
    private static void addImagesToPdf(AcroFields form, Map<String, byte[]> map, PdfStamper stamper) throws DocumentException {
        try {
            // 遍历图像资源映射中的每一个条目
            for (String key : map.keySet()) {
                byte[] bytes = map.get(key);
                log.info("开始将图片写入pdf........");
                // 检查当前条目键是否为预设的图像字段标识
                if ("image".equals(key) || "cardIdImgBack".equals(key)) {
                    // 获取图像所属页面的编号和位置信息
                    int pageNo = form.getFieldPositions(key).get(0).page;
                    Rectangle signRect = form.getFieldPositions(key).get(0).position;
                    float x = signRect.getLeft();
                    float y = signRect.getBottom();

                    // 根据字节数组创建图像对象
                    Image image = Image.getInstance(bytes);
                    // 获取在指定页面上层绘制内容的对象
                    PdfContentByte under = stamper.getOverContent(pageNo);
                    // 调整图像大小以适应指定的矩形区域
                    image.scaleToFit(signRect.getWidth(), signRect.getHeight());
                    // 设置图像的绝对位置
                    image.setAbsolutePosition(x, y);
                    // 将图像添加到页面上
                    under.addImage(image);
                }
            }
        } catch (IOException e) {
            // 捕获IO异常并抛出自定义服务异常
            throw new ServiceException("pdf导入图片异常:"+e.getMessage());
        }
    }

    /**
     * 将文本添加到PDF文档中
     *
     * 本方法通过使用给定的字体和字号，将Map中的键值对数据填充到PDF表单字段中
     *
     * @param form PDF文档的表单对象，用于操作PDF中的字段
     * @param data 包含需要填充到PDF中的数据的Map，Map的键对应PDF中的字段名
     * @param bf 用于设置PDF中文本的字体
     * @param stamper PDF印章对象，用于在PDF上添加内容
     */
    private static void addTextToPdf(AcroFields form, Map<String, Object> data, BaseFont bf, PdfStamper stamper) throws DocumentException {
        try {
            for (String key : data.keySet()) {
                // 设置字体属性
                form.setFieldProperty(key, "textfont", bf, null);
                // 设置字号属性
                form.setFieldProperty(key, "textSize", new Float(10), null);
                // 将Map中的值设置到对应的PDF字段中
                form.setField(key, data.get(key).toString());
            }

        } catch (IOException e) {
            // 如果在设置字体或字段属性时发生IO异常，则转换为ServiceException抛出
            throw new ServiceException("设置pdf字体异常:"+e.getMessage());
        }
    }

    /**
     * 导出pdf
     * @param pdfData
     * @param fileName
     */

    public static void createPdf(byte[] pdfData, String fileName) {
        try {
            HttpServletResponse response = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getResponse();

            // 设置响应头
            response.setContentType("application/pdf");
            response.setHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(fileName, "UTF-8") + ".pdf");
            // 设置字符编码
            response.setCharacterEncoding("UTF-8");
            try (OutputStream out = response.getOutputStream()) {
                out.write(pdfData);
            } catch (IOException e) {
                // 记录异常日志
                log.error("生成PDF时发生IO异常", e);
                throw new ServiceException("生成pdf异常: " + e.getMessage());
            }
        } catch (UnsupportedEncodingException e) {
            // 记录异常日志
            log.error("设置响应头时发生编码异常", e);
            throw new ServiceException("设置响应头时发生编码异常: " + e.getMessage());
        } catch (IOException e) {
            // 记录异常日志
            log.error("获取输出流时发生异常", e);
            throw new ServiceException("获取输出流时发生异常: " + e.getMessage());
        }
    }

}
